# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import abc
import datetime
import logging
import pprint
import sys

from tornado.ioloop import IOLoop

import coin.flow.subscriber as flow_subscriber
from coin.base.param_util import to_list
from coin.exchange.base.order_gateway import OrderSide
from coin.flow.simple_queue import SimpleQueue


def _print_working_orders(wo):
  for order in wo:
    if order.order_side == OrderSide.BUY:
      print(order.order_id, order.order_side, order.price, order.qty, order.accepted)
  for order in wo:
    if order.order_side == OrderSide.SELL:
      print(order.order_id, order.order_side, order.price, order.qty, order.accepted)


class UnaryStrategyBase(metaclass=abc.ABCMeta):
  ProductType = None
  LiveEnvironmentType = None
  OGName = "Unknown"

  def __init__(self, products, balance_currency=None, logger=None):
    self._logger = logger or logging.getLogger('UnaryStrategyBase')

    assert all([type(product) == self.ProductType for product in products]), products
    self._products = products

    if balance_currency is None:
      self._balance_currency = balance_currency
    else:
      self._balance_currency = sorted(to_list(balance_currency))
    self._balance_print_internval = datetime.timedelta(seconds=10)

    self._ioloop = None
    self._queue = None
    self._flow_sub = None

    self._og = None

    self._book = None

  def is_og_ready(self):
    return (self._og is not None and self._og.is_ready())

  @property
  def order_gateway(self):
    return self._og

  def on_base_init(self, ioloop, queue, flow_sub):
    self._ioloop = ioloop
    self._queue = queue
    self._flow_sub = flow_sub

  def on_og_reset(self, name, og):
    self._logger.info('OG initialized: %s', name)
    assert name == self.OGName, name
    self._og = og

    if self._og is not None:
      self._start_print_balance()
      self.init_executor()

  def on_book_reset(self, name, book_builder):
    self._logger.info('Feed initialized: %s', name)
    assert name == self.OGName, name
    for product in self._products:
      book_builder.subscribe(product, self._on_book)

  def _print_balance(self):
    print('%s ------------------------' % str(datetime.datetime.now()))
    if self.is_og_ready():
      pprint.pprint(self._og.get_positions())
      pprint.pprint(self._og.get_balances())
      # pprint.pprint(self._og.get_working_order())
      _print_working_orders(self._og.get_working_order())
    else:
      pprint.pprint("order gateway is not ready")
    sys.stdout.flush()

  def _start_print_balance(self):
    try:
      self._print_balance()
    finally:
      self._ioloop.add_timeout(self._balance_print_internval, self._start_print_balance)

  def _on_book(self, book):
    self._book = book
    self.on_book()

  @abc.abstractmethod
  def init_executor(self):
    raise NotImplementedError()

  @abc.abstractmethod
  def on_book(self):
    raise NotImplementedError()

  @classmethod
  def run_live(cls,
               *,
               products,
               og_config_name,
               on_base_init_callback,
               on_book_reset_callback,
               on_order_gateway_reset_callback,
               init_order_gateway_after=None,
               exit_after=None,
               og_params=None):
    # Initialize product
    products = to_list(products)
    for p in products:
      assert isinstance(p, cls.ProductType)

    # Initialize loop
    ioloop = IOLoop.current()
    queue = SimpleQueue('queue')
    flow_sub = flow_subscriber.from_queue(queue)
    on_base_init_callback(ioloop, queue, flow_sub)

    # Initialize first
    env = cls.LiveEnvironmentType(products[0].base,
                                  products,
                                  products,
                                  og_config_name,
                                  og_params=og_params,
                                  init_og_after=init_order_gateway_after,
                                  ioloop=ioloop,
                                  queue=queue,
                                  flow_sub=flow_sub)
    env.on_book_reset_callback.append(on_book_reset_callback)
    env.on_order_gateway_reset_callback.append(on_order_gateway_reset_callback)

    env.start()

    if exit_after is not None:
      ioloop.add_timeout(exit_after, ioloop.stop)

    try:
      ioloop.start()
      return 0
    except KeyboardInterrupt:
      return 1
